package com.qf.cx.simplenms.monitor;

import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.gson.Gson;
import com.qf.cx.simplenms.common.utils.SetUtils;
import com.qf.cx.simplenms.link.api.LinkManager.LinkType;
import com.qf.cx.simplenms.link.api.LinkStatus;
import com.qf.cx.simplenms.link.api.LinkStatus.State;
import com.qf.cx.simplenms.monitor.model.HeartBeatReport;
import com.qf.cx.simplenms.monitor.model.ReaderInfo;
import com.qf.cx.simplenms.monitor.model.TopicInfo;
import com.qf.cx.simplenms.persist.entity.AntennaEntity;
import com.qf.cx.simplenms.persist.entity.ReaderEntity;
import com.qf.cx.simplenms.persist.entity.ReaderGroupEntity;
import com.qf.cx.simplenms.persist.entity.SubscriberEntity;
import com.qf.cx.simplenms.persist.entity.TopicEntity;
import com.qf.cx.simplenms.persist.service.api.SubscriberService;
import com.qf.cx.simplenms.subscriber.api.HttpChannel;
import com.qf.cx.simplenms.subscriber.impl.HttpChannelImpl;
import com.qf.cx.simplenms.subscriber.type.exception.InvalidURIException;

@Component
public class HeartBeatManager implements Runnable {

	private final Logger LOG = LoggerFactory.getLogger(this.getClass());
	@Autowired
	private SubscriberService subscriberService;
	@Autowired
	private LinkStatus link;
	private Map<String, State> readerState;
	private Map<String, HttpChannel> urlChns = new HashMap<String, HttpChannel>();
	private Set<String> oldurls = new HashSet<String>();
	private Set<String> currenturls = new HashSet<String>();

	private int cycle = 5000;

	public HeartBeatManager() {
		super();
		LOG.info("heart beat manager start");
		new Thread(this, "HeartBeatManager").start();
	}

	@Override
	public void run() {

		while (true) {
			try {
				TimeUnit.MILLISECONDS.sleep(cycle);
				getLinkState();
				sendAllReport();
			} catch (InterruptedException e) {
				LOG.warn("HeartBeatManager stopped");
				return;
			}
		}

	}

	private void getLinkState() {
		readerState = link.getLinkStatus();
	}

	private void sendAllReport() {
		List<SubscriberEntity> sbs = subscriberService.getSubscribers();
		if (sbs == null) {
			LOG.warn("heartbeat Subscribers null");
			return;
		}
		currenturls = getUrls(sbs);
		for (SubscriberEntity sb : sbs) {
			sendSubscriberReport(sb);
		}
		closeUnusedChannel(currenturls, oldurls);
		oldurls = currenturls;
	}

	private void closeUnusedChannel(Set<String> currenturls, Set<String> oldurls) {
		Set<String> urls = SetUtils.getComplementary(oldurls, currenturls);

		for (String url : urls) {
			if (urlChns.containsKey(url)) {
				urlChns.get(url).close();
				urlChns.remove(url);
			}
		}
	}

	private Set<String> getUrls(List<SubscriberEntity> subEns) {
		Set<String> urls = new HashSet<String>();
		for (SubscriberEntity subEn : subEns) {
			String url = subEn.getStatusUrl();
			if (url != null) {
				urls.add(subEn.getStatusUrl());
			}
		}
		return urls;
	}

	private void sendSubscriberReport(SubscriberEntity subEn) {
		String url = subEn.getStatusUrl();
		if (url == null) {
			return;
		}

		HttpChannel chn = null;
		if (urlChns.containsKey(url)) {
			chn = urlChns.get(url);
		} else {

			try {
				chn = new HttpChannelImpl(url);
			} catch (InvalidURIException e) {
				LOG.error("heartbeat report url {} invalid", url);
				e.printStackTrace();
				return;
			}
			urlChns.put(url, chn);
		}

		HeartBeatReport report = getSubscriberReport(subEn);

		try {
			int statusCode = chn.post(new Gson().toJson(report));
			if (statusCode != HttpStatus.SC_OK) {
				LOG.error("send heartbeat report to {} failed, status code = {}", url, statusCode);
			}
		} catch (IOException e) {
			LOG.error("send heartbeat report to {} failed", url, e);
		}
		return;
	}

	private HeartBeatReport getSubscriberReport(SubscriberEntity subEn) {
		List<TopicEntity> topEns = subEn.getTopicEntities();
		HeartBeatReport report = new HeartBeatReport();
		Set<TopicInfo> topicInfo = new HashSet<TopicInfo>();

		ZonedDateTime date = ZonedDateTime.now();
		report.setReportCreateTime(date.toInstant().toEpochMilli());
		report.setGmt(date.getOffset().toString());
		for (TopicEntity topEn : topEns) {
			topicInfo.add(getTopicInfo(topEn));
		}
		report.setTopicInfo(topicInfo);

		LOG.debug("sub: {} Report: {}", subEn.getStatusUrl(), report);
		return report;
	}

	private TopicInfo getTopicInfo(TopicEntity topEn) {
		List<ReaderGroupEntity> rgEns = topEn.getReaderGroupEntities();
		TopicInfo topicInfo = new TopicInfo();
		Set<ReaderInfo> readerInfo = new HashSet<ReaderInfo>();

		topicInfo.setName(topEn.getName());

		for (ReaderGroupEntity rgEn : rgEns) {
			readerInfo.addAll(getReaderGroupReaders(rgEn));
		}
		topicInfo.setReaderInfo(readerInfo);
		return topicInfo;
	}

	private Set<ReaderInfo> getReaderGroupReaders(ReaderGroupEntity rgEn) {
		Set<ReaderInfo> rds = new HashSet<ReaderInfo>();
		List<AntennaEntity> antEns = rgEn.getAntennaEntities();
		for (AntennaEntity antEn : antEns) {
			ReaderEntity rdEn = antEn.getReaderEntity();
			ReaderInfo rinfo = new ReaderInfo(rdEn.getName(), rdEn.getIp(), rdEn.getPort(), rdEn.getDeviceSn(), 0);
			State st = readerState.get(LinkType.valueOf(rdEn.getLinkType()).getReaderIdentifier(rdEn.getIp(),
					rdEn.getPort(), rdEn.getDeviceSn()));
			if (st != null) {
				rinfo.setLinkState(st.getValue());
			}
			rds.add(rinfo);
		}
		LOG.debug("ReaderGroup {} readerInfo {}", rgEn.getName(), rds);
		return rds;
	}

}
